package com.veetao.api.config;

import java.io.IOException;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.GZIPOutputStream;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.veetao.api.utils.ConfigUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.ModelAndView;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.veetao.api.consts.McpConstants;
import com.veetao.api.model.ApiCommandContext;
import com.veetao.api.model.ApiResult;
import com.veetao.api.model.CommandContext;
import com.veetao.api.utils.McpThreadLocal;
import com.veetao.api.utils.McpUtils;

@Component
public class ApiReturnValueHandler implements HandlerMethodReturnValueHandler {

    private static final Log logReqResp = LogFactory.getLog("mcp_req_resp");

    public static Gson gson = new GsonBuilder().serializeNulls().disableHtmlEscaping().serializeSpecialFloatingPointValues().create();
    private static WeakReference<ConcurrentHashMap<Object, String>> Obj2JsonCache = new WeakReference<ConcurrentHashMap<Object, String>>(
            new ConcurrentHashMap<Object, String>());

    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        final boolean isSupport = ApiResult.class.equals(returnType.getParameterType());
        if (isSupport)
            return true;
        return ModelAndView.class.equals(returnType.getParameterType());
    }

    private static ObjectMapper mapper = new ObjectMapper();

    private static String serializeObject(Object obj) {
        try {
            return mapper.writeValueAsString(obj);
        } catch (JsonProcessingException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        return "{\"code\":5}";
    }

    private static final String RESP_ERROR_JSON = serializeObject(new ApiResult(1, "system error"));
    private static final byte[] RESP_ERROR = RESP_ERROR_JSON.getBytes();
//	private static final ResourceBundle rsMessage = ResourceBundle.getBundle("api_result_code_messages");

    @Override
    public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer,
                                  NativeWebRequest webRequest) throws IOException {
        mavContainer.setRequestHandled(true);
        final HttpServletResponse resp = webRequest.getNativeResponse(HttpServletResponse.class);
        if (returnValue == null || !ApiResult.class.isInstance(returnValue)) {
            return;
        }
        final ApiResult apiResult = (ApiResult) returnValue;
        if (apiResult.getData() == null) {
            return;
        }
        final HttpServletRequest req = webRequest.getNativeRequest(HttpServletRequest.class);
        final boolean isGZ = "1".equals(req.getParameter(McpConstants.PARAM_GZ));
        final String jsCall = req.getParameter(McpConstants.PARAM_JS_CALL);
        resp.setStatus(HttpServletResponse.SC_OK);

        OutputStream os = null;
        final CommandContext cctx = McpThreadLocal.thCommandContext.get();
        try {
            if (cctx != null) {
                cctx.addStatExt("rtncode", apiResult.getCode());
            }
            String resultJson;
            if (apiResult.getCode() == 0) {

                final Object resultO = buildObjResult(apiResult.getData());
                if (McpUtils.isJsonString(resultO) || McpUtils.isHtmlString(resultO)) {
                    resultJson = (String) resultO;
                } else {
                    resultJson = serializeObject(resultO);
                }
            } else {
                resultJson = serializeObject(buildObjResult(apiResult));
            }
            if (isGZ) {
                resp.setContentType("application/json-gz");
                resp.setHeader("content-encoding", "gzip");
                os = new GZIPOutputStream(resp.getOutputStream());
            } else {
                if (jsCall != null && jsCall.length() > 0) {
                    resp.setContentType("text/javascript;charset=UTF-8");
                } else if (McpUtils.isJsonString(resultJson)) {
                    resp.setContentType("text/plain;charset=UTF-8");
                } else if (McpUtils.isHtmlString(resultJson)) {
                    resp.setContentType("text/html;charset=UTF-8");
                } else {
                    resp.setContentType("text/plain;charset=UTF-8");
                }
                os = resp.getOutputStream();
            }

            final String apiName = (String) req.getAttribute(McpConstants.REQ_API_METHOD_NAME);
            final Object psMap = req.getAttribute(McpConstants.REQ_ATTR_PerSMAP);
            final String reqJson = gson.toJson(psMap);
            Object statExt = null;
            {
                if (cctx != null) {
                    if (ApiCommandContext.class.isInstance(cctx)) {
                        ApiCommandContext acc = (ApiCommandContext) cctx;
                        statExt = acc.getStatExt();
                    }
                }
            }
            if (resultJson != null) {
                if (jsCall != null && jsCall.length() > 0) {
                    final String resultJsCall = String.format("%s(%s);", jsCall, resultJson);
                    os.write(resultJsCall.getBytes("UTF-8"));
                } else {
                    os.write(resultJson.getBytes("UTF-8"));
                }

                if (ConfigUtils.isProductEnv()) {
                    int l = resultJson.length();
                    int maxLen = 300;
                    if (l > maxLen) {
                        logstat(apiName, reqJson, resultJson.substring(0, maxLen - 5) + "..." + resultJson.substring(l - 5, l), statExt);
                    } else {
                        logstat(apiName, reqJson, resultJson, statExt);
                    }

                } else {
                    logstat(apiName, reqJson, resultJson, statExt);

                }

            } else {
                os.write(RESP_ERROR);
                logstat(apiName, reqJson, RESP_ERROR_JSON, statExt);
            }
            os.flush();
        } finally {
            if (os != null) {
                try {
                    os.close();
                } catch (Exception e) {

                }
            }
        }

    }


    private static final String RF_STAT_LOG = "{\"api\":\"%s\",\"time\":%s,\"req\":%s,\"resp\":%s,\"ext\":%s}";

    /**
     * 记录统计日志
     *
     * @param apiName
     * @param reqJson
     * @param resultJson
     */
    private final void logstat(String apiName, String reqJson, String resultJson, Object statExt) {
        if (apiName == null || apiName.isEmpty()) {// 忽略null和空的请求日志
            return;
        }
        final String extJson = statExt == null ? "{}" : gson.toJson(statExt);
        final long time = System.currentTimeMillis();
        logReqResp.info(String.format(RF_STAT_LOG, apiName, time, reqJson, resultJson, extJson));

    }

    private static String getJsonFromCache(Object apiResultData) {
        ConcurrentHashMap<Object, String> map = Obj2JsonCache.get();
        if (map == null) {
            return null;
        }
        return map.get(apiResultData);
    }

    private static void setJsonToCache(Object apiResultData, String json) {
        ConcurrentHashMap<Object, String> map = Obj2JsonCache.get();
        if (map == null) {
            map = new ConcurrentHashMap<Object, String>();
            Obj2JsonCache = new WeakReference<ConcurrentHashMap<Object, String>>(map);
        }
        map.putIfAbsent(apiResultData, json);
    }


    public static Object buildObjResult(Object result) {
        if (result == null) {
            return null;
        }
        if (McpUtils.isJsonString(result)) {
            return result;
        }
        if (McpUtils.isHtmlString(result)) {
            return result;
        }
        if (result instanceof Boolean) {
            result = (Boolean) result ? 0 : 1;
        }
        if (result instanceof Integer || result instanceof Long || result instanceof String) {
            Map<String, Object> rtMap = new HashMap<String, Object>();
            rtMap.put("result", result);
            return rtMap;
        }
        return result;
    }


}
